I am following the Todo example of redux from here https://github.com/reactjs/redux/tree/master/examples/todos
I have made some minor changes. I am accepting more fields in my todo.
{text, time, by} and displaying these details in a table.
I want to order this table by time. I do not want to follow the redux pattern for this use case. Why you ask!
For something as simple as ordering I do not want to add this in the state. I want to maintain these functions within the React state itself. Since the VisibleTodoList is a smart component where its state has the todos I want to be able to reorder it the way I like to so.
My VisibleTodoList.js
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
const orderTime = (todos) => {
console.log('inside order time!'); //Displays this. comes inside here.
return todos.filter(t => t.time > 20) //This gives me error!!
}
const mapStateToProps = (state) => ({
todos: getVisibleTodos(state.todos, state.visibilityFilter), // I do not want to add more things to the state. Want to keep it simple.
})
const mapDispatchToProps = ({
onTodoClick: toggleTodo,
onTimeClick: orderTime //My function defined above to returned filtered todos.
})
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
TodoList.js Looks like this
const TodoList = ({ todos, persons, onTodoClick, completed, onTimeClick}) => (
<div>
<table>
<tbody>
<tr>
<th>Task Name</th>
<th
onClick={() => onTimeClick(todos)}
style={{
textDecoration: completed ? 'line-through' : 'none'
}}>Time</th>
<th>By person</th>
</tr>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
/>
)}
</tbody>
</table>
</div>
)
My todo.js looks almost the same
const Todo = ({ onClick, completed, text, time, by }) => (
<tr key={text}>
<td style={{
textDecoration: completed ? 'line-through' : 'none'}}>{text}</td>
<td>{time}</td>
<td>{by}</td>
</tr>
)
I keep getting this error when I click on Time column
Actions must be plain objects. Use custom middleware for async actions.
What am I doing wrong?
Also what pattern should I be following to achieve this? Without deviating too much from the redux pattern. Will I have to use setState
To give more context I want to have a local UI state than having it in the redux store. Like explained here
Should you ever use this.setState() when using redux?
Update 1: I do understand that because orderTime is not dispatching a object it is complaining. But my broader question if I had to do the achieve the same functionality. How would I have to do it?
If I understand correctly I will have to use setState to do this.
This is the best solution I could come up with so far!
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'WEIGHT_SORT':
return _.sortBy(todos, [function(o) { return o.time; }]);
default:
throw new Error('Unknown filter: ' + filter)
}
}
class TodoList extends React.Component {
state = { filter: 'SHOW_ALL' }
handleShow = filter => {
this.setState({ filter: 'WEIGHT_SORT' })
}
render(){
const filteredTodos = getVisibleTodos(todos, this.state.filter)
return (
<div>
<table>
<tbody>
<tr>
<th>Task Name</th>
<th
onClick={() => this.handleShow('SHOW_COMPLETED')}>Time</th>
<th>By person</th>
</tr>
{filteredTodos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => this.props.onTodoClick(todo.id)}
/>
)}
</tbody>
</table>
</div>
)
}
}
This being UI tweaks(ordering, sorting & other manipulations) we keep it in the React local state. This leads to cleaner code because our redux state is clean and does not store all of this information.
Open to suggestions/comments and a better answer!
Related
I'm using Svelte for the first time, and I'm trying to figure out how to apply filtering to a list retrieved from an API. My EventsIndex component is very simple, and comprises the following logic:
import FetchPosts from '../services/postsByType'
import EventCard from '../components/EventCard.svelte'
export let post_type
let events = new FetchPosts( post_type ).get()
let currentCategory = 0
function updateCategory( catId ) {
currentCategory = catId
}
Everything works just as expected.
I'm also using the await template syntax to display the relevant data:
{#await events}
<h1>loading...</h1>
{:then events}
<section class="grid">
{#each events as event}
<EventCard event={event} on:filter={updateCategory}/>
{/each}
</section>
{:catch error}
<p>Something went wrong...</p>
{/await}
The missing piece is the reactive filtered list. I thought I could do the following, but it generates the error events.map is not a function, which makes sense, as the events variable would be an unresolved promise when filteredEvents is invoked.
$: filteredEvents = () => {
if (currentCategory === 0) return events
return events.filter( event => {
return event.categories !== null
? event.categories.some( cat => cat.id == currentCategory )
: false
})
}
So how would I add a reactive filteredEvents function, that I can also include in my template?
Filtering on the events inside the {#each} loop should work. If the filter function was very short like this I'd apply it inline
{#each events.filter(e => e.id === currentId) as event}
Just as an example. Since in your case the filter function is more complex, moving it to a function might be the better option
filterEvents(events, currentCategory) {
if (currentCategory === 0) return events
return events.filter( event => {
return event.categories !== null
? event.categories.some( cat => cat.id == currentCategory )
: false
})
}
</script>
...
{#each filterEvents(events, currentCategory) as event}
(I think you can replace this
return event.categories !== null
? event.categories.some( cat => cat.id == currentCategory )
: false
by this
event.categories?.some( cat => cat.id === currentCategory )
Background
I'm trying to build an app which shows a number of stores, in the home screen which is a function component (mind this as I need to use hooks) I have a scroll view which shows different stores.
What I need
When the user presses on one of the stores it should redirect it to a screen which has the information of that specific store. I have built the "store detail" screen but with static info, I want to replace all of that information with data stored in a firestore collection.
Question
How would one go about retrieving data from a Firestore collection in react native, then assigning the data from each document to a separate Touchable Opacity (I know about passing params with react navigation, I just don't know which param to pass when working with Firestore), and then displaying that data in the store detail screen?
Sample code for context
App.js
<NavigationContainer>
<Stack.Navigator initialRouteName={user ? 'Home' : 'Login'}
screenOptions={{cardStyle: { backgroundColor: '#FFFFFF' }}}>
<Stack.Screen name="Home"options={{headerShown: false}}>
{props => <HomeScreen {...props} extraData={user} />}
</Stack.Screen>
<Stack.Screen name="Login" component={LoginScreen} options={{headerShown: false}}/>
<Stack.Screen name="Registration" component={RegistrationScreen} options={{headerShown: false}}/>
<Stack.Screen name="storeDetail" options={{title: ''}}>
{props => <storeDetail {...props} extraData={} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
In this file you'll see that I've already called some data (Login and Register pass userData to the Home Screen), however in order to implement that method I depended on the response from the authentication method I was using. I imagine although, I will probably need to pass something as extraData, I understand what I should do, I just don't know how to fill the blank spaces.
Thanks a lot in advance!
First, install the Firebase SDK in your app, so you can make queries to your backend.
I don't know if your sample App.js represents the current state of progress on your app, but I'm going to assume that:
you already have your storeDetail screen built
you know the store's id before navigating to the screen (eg in the HomeScreen)
you pass the storeId as a navigation param when navigating to storeDetail
So in storeSetails screen, you can query Firestore when receiving storeId, and save the result to a state variable on success:
const StoreDetailsScreen = ({ route }) => { // route is passed as a prop by React Navigation
const { storeId } = route.params
const [store, setStore] = useState()
const [loading, setLoading] = useState(true) // show a loading spinner instead of store data until it's available
useEffect(() => {
const fetchQuery = async () => {
const storeData = await firestore()
.collection('stores')
.doc(storeId)
.get() // this queries the database
if (storeData) {
setStore(storeData) // save the data to store state
setLoading(false) // set loading to false
} else {
// something went wrong, show an error message or something
}
}
fetchQuery()
}, [storeId])
if (loading) {
return (
<ActivityIndicator/>
)
}
return (
// ... store details
)
}
Then you can use the data in store to render stuff in your screen
<Text>{store.name}</Text>
<Text>{store.email}</Text>
// ...
More info about how to use Firestore in RN: https://rnfirebase.io/firestore/usage
I have been trying to render information from my firebase to a react native component. I started by console logging what I have done, the data is being fetched completely fine:
displayAllPlayers(){
dbh.collection('Players').get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.data().First, doc.data().Last)
})
})
}
I then tried to add this information to my component as follows:
displayAllPlayers(){
dbh.collection('Players').get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
<Player key={doc.data().First} fName={doc.data().First} lName={doc.data().Last} />
})
})
}
render() {
const myPlayers = this.displayAllPlayers()
}
return(
{myPlayers}
)
However, I am unable to see anything on my screen. Any clues why?
thanks
React works a bit differently. You need to save the async call response into the state, and then in the render loop check if the state is there, you loop trough it and print the players.
Here is a similar question that has already answered.
I quote the main points that might be useful for you:
Save the data to state when data loaded
render() {
const myPlayers = this.displayAllPlayers()
let data = doc.data();
this.setState({ data: myPlayers });
}
And then render them as you want:
render() {
let dataUI = this.state.data ? <h1>No Data</h1> : <pre>{JSON.stringify(this.state.data)}</pre>;
return (
<div className="classname">
<h1>... </h1>
<div>
<h1>UI Data</h1>
{dataUI}
</div>
</div>
);
}
Also this is a tutorial about Getting started with Cloud Firestore on React Native. Here is another tutorial a little more explained: Create React Native Firebase CRUD App with Firestore.
I encourage you to check both links to have a better understanding.
I'm trying to disable the toolbars for all core blocks to keep the editors from unnecessarily formatting the content. Is that even possible?
My current approach is:
wp.blocks.getBlockTypes().forEach((blockType) => {
// unregister all default styles (from the right sidebar)
let blockName = blockType.name;
if ( blockType.hasOwnProperty('styles')) {
blockType.styles.forEach( (style) => {
wp.blocks.unregisterBlockStyle( blockName, style.name );
});
}
});
Can I somehow access the toolbars in this loop? Do I understand it correctly that I have to override the edit and save methods of the core blocks, probably with a filter?
Thanks, Patrik
I have just solved the problem, but different than intended.
Basically, the solution for me was to deregister the required core blocks, make changes to the edit and save methods, and then re-register the blocks.
A great help was this blog article by Riad Benguella:
https://riad.blog/2017/10/16/one-thousand-and-one-way-to-extend-gutenberg-today/
Here's an example based on a core / quote block:
const TextControl = wp.components.TextControl;
import './style.scss';
import './editor.scss';
wp.domReady( () => {
let unregisteredBlock = wp.blocks.unregisterBlockType('core/quote');
unregisteredBlock.title = 'Quotation';
unregisteredBlock.icon = 'format-quote';
unregisteredBlock.edit = ({ attributes, setAttributes} ) => {
const updateFirstValue = ( val ) => {
setAttributes({
value: val
});
};
const updateSecondValue = ( val ) => {
setAttributes({
citation: val
});
};
return (
<div>
<TextControl
label='Quote'
value={ attributes.value }
onChange={ updateFirstValue }
/>
<TextControl
label='Citation'
value={ attributes.citation }
onChange={ updateSecondValue }
/>
</div>
);
};
unregisteredBlock.save = ( { attributes, className } ) => {
return (
<blockquote className={className}>
<p>{attributes.value}</p>
<cite>{attributes.citation}</cite>
</blockquote>
)
};
wp.blocks.registerBlockType('core/quote', unregisteredBlock);
});
In principle, both the edit and save method are replaced here, only the block attributes are reused from the core blocks. Due to the fact that new elements are used to enter the content, the toolbars are not the problem anymore.
I hope this can help someone who has the same problem.
Cheers,
Patrik
I have the following problem with some implementation in React/Redux.
After clicking on a button, a specific redux action is call and div with notification shows on screen. You can close this notification by clicking on a (X) sign on that div (another redux action) or notification will close automatically after 5 secs. Clicking on (x) should cancell an automatic action.
actions:
const OPEN = 'show_notification';
const CLOSE = 'close_notification';
const CLOSE_AUTO = 'close_auto';
function showNotification(data) {
return {
type: 'OPEN',
data
}
}
function closeNotification(index) {
return {
type: 'CLOSE',
index
}
}
function closeAuto() {
return {
type: 'CLOSE_AUTO'
}
}
epics:
import (...)
closeNotificationAuto = action$ => action
.filter(action => action.type === OPEN)
.mergeMap(action => action
.delay(5000)
.map( () => closeAuto)
.takeUntil(action$.ofType(CLOSE))
}
Anyway, when two notifications are on screen, the action === CLOSE is closing the first one, and cancell delay() for another.
Not posting my whole code because the problem is here, in epics. Can't manage to achieve a solution:
when clicking on a (x) the specific notification is close, but another one (which time is for example 3secs) is still visible and hide automatically after another 2 secs.
Thans for any help!
The code in the epic is incomplete, so it's not totally clear (what happens inside the mergeMap?). But I did see one issue, which is that your takeUntil is on the top-level observable chain, which means it won't just cancel that particular delay, it will also stop listening for any action at all.
Instead, you need to delay and cancel the matched action individually inside something like a mergeMap, switchMap, etc. This is commonly called "isolating your observer chains".
Here's what that might look like:
const closeNotificationAuto = action$ =>
action$
.ofType(OPEN)
.mergeMap(action =>
Observable.of(action)
.delay(5000)
.map(() => closeAuto())
.takeUntil(action$.ofType(CLOSE))
);
This pattern, filter then flatMap (mergeMap, switchMap, etc), is how most of your epics will look.
Regarding your comments below, it sounds like you want to add a filter to takeUntil notifier to only take CLOSE actions that somehow uniquely identifies it.
See https://stackoverflow.com/a/48452283/1770633
const closeNotificationAuto = action$ =>
action$
.ofType(OPEN)
.mergeMap(action =>
Observable.of(action)
.delay(5000)
.map(() => closeAuto())
.takeUntil(
action$.ofType(CLOSE).filter(a => a.something === action.something)
)
);
If there isn't some sort of unique ID already available for each, you'll need to create and include one.