Displaying filtered data in React - reactjs

Ok, so I recently had a take-home coding challenge which I failed. One of the reasons was I couldn't figure out how to implement filtering in React using checkboxes. So I resorted to doing the filtering using CSS, but they wanted me to do using react state.
const [data, setData] = useState([]);//api call data saved here
const [publisher, setPublisher] = useState({// this state checks which checkboxes are selected or not
Nin: false,
MS: false,
SN: false
});
So for the assignment, I had to make an API call, which would give me more than 1000 items in the response. I had to filter that object to only have 3 types of products saved up in State, which I did with no problem.
useEffect(() => {
fetchJsonp("apicall", {
jsonpCallback: "jsonp"
})
.then((response) => response.json())
.then((json) =>
setData(
json.filter(
(x) =>
x["Publisher"] === "Nintendo" ||
x["Publisher"] === "Sony" ||
x["Publisher"] === "Microsoft"
)
)
)
.catch((err) => console.log(err));
}, []);
The issue was that once the Cards were showing on the screen, which was absorbing data from the local state. The user had the option to filter via a checkbox to only see the type of products from Nintendo, Microsoft, or Sony, or all games would show if no checkbox was selected.
So my question is how would one go about implementing this feature in react?

Just add a filter function in your render. I'm assuming for the cards you were using map so just do something like
return (
<>
{data.filter((item)=>{
const {Nin, MS, SN} = publisher;
if(!Nin && !MS && !SN){ //return all if no boxes checked
return true;
}
else{
return (Nin && item.publisher === 'Ninetndo') ||
(SN && item.publisher === 'Sony') ||
(MS && item.publisher === 'Microsoft')
}
}).map((x) => <Card />)}
</>
)

Related

Only rerender and fetch when api property has changed

Im trying to get the use effect to rerender only when the labels property has changed.
i want to fetch newest changes in the labels property only when there is a change.
My code below:
const usePrevious = (value: any) => {
const ref = useRef()
useEffect(() => {
ref.current = value
}, value)
return ref.current
}
const prevLabels = usePrevious(labels)
useEffect(() => {
if (labels !== prevLabels) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
const fetchUpdatedLabels = async () => {
await axios
.get(`/events/${printEvent.id}`)
.then((response) => {
const newLabels = response.data.labels
// filter response to empty array if there are no splits left
// if is null return empty string
let fetchedLabels =
newLabels !== null
? newLabels
.toString()
.split(',')
.filter((label: string) => {
return label !== ''
}) || ''
: ''
getLabels(fetchedLabels)
print.updateEvent(printEvent.id, 'value', printEvent.value)
})
.catch((err) => {
console.error(err, 'cant fetch labels from api')
})
}
it keeps refetching nonstop, how do i achieve this?
This is most likely in part because you are comparing an array with an array using the equivalence operator !==. It can be surprising to people new to JS but if you do this in a JS console:
[1,2,3] === [1,2,3]
It returns false. The reason is that an array is an object and what you are asking is "is the array on the left literally the same as the one on the right" -- as in the same variable. Whereas these are 2 separate array instances that happen to have the same contents. You might wonder why it works then on strings and numbers etc, but that's because those are primitive values and not objects which are instantiated. It's a weird JS gotcha.
There are several ways to compare an array. Try this:
useEffect(() => {
if (labels.sort().join(',') !== prevLabels.sort().join(',')) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
I'm not sure about your use case here though as when the labels are different, you fetch them, but the only way they can change is to be fetched? Is there something else in the code that changes the labels? If so, won't they get wiped out by the ones from the server now as soon as they change?
Something else is wrong here I think. Where do you set labels into state?

useEffect removes state and then resets? React

I have a component made with React Select. The options passed into the options prop on the Select depends upon state that users entered previously. Every time the component renders there are checks to see if selectOptions already includes items from the state array
<Select
styles={err === '' ? inputStyles : inputStylesErr}
className="basic-single"
classNamePrefix="select"
isClearable={true}
isSearchable={true}
isMulti={true}
placeholder={`Select or search health zones in ${province}, ${state.address.country}`}
options={selectOptions}
defaultValue={selectOptions.some((option) => option.value === state.healthZonesServed[0]) ? (
state.healthZonesServed.map((zone) => {
return { ['label']: zone, ['value']: zone }
})
) : ''}
onChange={(values) => handleAddHealthZones(values.map((value) => value.value))}
/>
const handleAddHealthZones = (value) => {
setState({
...state,
healthZonesServed: value
})
}
If a user populated their healthZonesServed array and then goes back and changes their province (the piece of state which controls the selectOptions) and then goes back to this component I need the healthZonesServed array to be reset to []
I do this in a useEffect. I can see in my console.log the healthZonesServed resets to an empty array on page load then somehow re-populates its previous values from somewhere. Would anyone have any insight as to why this is happening and a possible solution?
useEffect(() => {
if (selectOptions.some((option) => option.value === state.healthZonesServed[0])) {
return
} else {
setState({
...state,
healthZonesServed: []
})
console.log('HIT')
}
}, [])
The most probable reason, why this is not working is because you are using setState in a functional component. Try using the useState hook for the purpose of managing the state, in your case, setting the heathZoneServed array to empty array.
const [healthZoneServed,sethealthZoneServed] = useState([]);
sethealthZoneServed(value);
useEffect(() => {
if (selectOptions.some((option) => option.value === state.healthZonesServed[0])) {
return;
} else {
sethealthZonesServed([]);
console.log('HIT');
}
}, [healthZonesServed]);
Hope this was helpful.

How to code a good re-render parent call?

render my parent component from a call in its child component. But I don't know how to write it correctly.
The context is: When the page is loaded, the code search the userId in localStorage to download the data of a user to display its information.
A link exists in child component to removed the idUser and got an empty form. The problem is that my parent don't re-render automatically.
<Link to="/consultation" onClick={() => {localStorage.removeItem('idUser'); this.props.callBack();}}>here.</Link>
-
else if(user){contents = [<Message_UserIdentified user callBack={this.forceUpdate()}/>, contentform];}
I tried something, but it's not work. Could you help me please ?
I don't know how to parse it (the item "user" and in same time the callBack).
The code write me that it don't find the function forceUpdate().
This is my parents page called "Consultation" (I removed some parts of code to be clearer):
import { Message_EmptyUserID, Message_NeedToBeConnected, Message_UserIdentified } from '../component/message_consultation';
const Consultation = () => {
const [user, setUser] = useState(null);
const [login] = useState(jwtUtils.checkToken());
const [userId, setUserId] = useState(false);
function handleChange(event) {
setUser({
...user,
[event.target.name]: event.target.value
})
};
useEffect(() => {
if (login === false || localStorage.getItem("idUser") === null) return;
axios.get(`http://localhost:5000/users/${localStorage.getItem("idUser")}`, {
headers: {
'token': localStorage.getItem('token')
}
})
.then(res => {
setUser(res.data);
if(res.data !== undefined){
setUserId(true);
}
})
.catch(error => console.log(error))
}, []);
let contents, contentform;
contentform = (
<div >...
</div>
);
if (login === false) contents = Message_NeedToBeConnected();
else if (userId === false) contents = contentform;
else if(user){contents = [<Message_UserIdentified user callBack={this.forceUpdate()}/>, contentform];}
return (
<div className="case card border-secondary mb-3" styles="max-width: 20rem;">
<div className="card-header">Consultation</div>
<div className="card-body">
{contents}
</div>
</div>
);
}
export default Consultation;
This is my child component called "Message_UserIndentified":
const Message_UserIdentified = (user) => {
return(
<Alert color="primary" className="alert alert-dismissible alert-info">
<h4>{user === null || user === undefined ? "" : user.firstname} {user === null || user === undefined ? "" : user.lastname}</h4>
If you are not {user === null || user === undefined ? "" : user.firstname} and you are <mark>already registered</mark>, find your consultation <Link to="/register" onClick={localStorage.removeItem('idUser')}>here.</Link> <hr/>
If you are not {user === null || user === undefined ? "" : user.firstname} and your are <mark>not registered</mark>, add your consultation <Link to="/consultation" onClick={() => {localStorage.removeItem('idUser'); this.props.callBack();}}>here.</Link>
</Alert>
);
}
First of all, you mix up the hooks API with the class API. You need to choose one, React doesn't allow to use both in a single component.
React functional components don't have this and this.forceUpdate. See the official note on how to force update in a functional component with hooks:
const Consultation = () => {
const [ignored, forceUpdate] = React.useReducer(x => x + 1, 0);
// ...
return <Message_UserIdentified user callBack={forceUpdate} />;
};
Second, you mustn't call the function when you want to pass it as a callback. When you run callBack="forceUpdate()", the function is executed once and the returned value is passed to the child prop. You need to run callBack="forceUpdate" to pass the function itself so that the child component can execute it whenever it wants.
This rule is applicable to class components too, but you should also bind the forceUpdate method to the current component:
<button onClick={() => this.forceUpdate()}>Demo</button>
// or
<button onClick={this.forceUpdate.bind(this)}>Demo</button>
From the architecture point of view, you shouldn't use localStorage to share data between components of the application because localStorage isn't reactive (it doesn't notify when a content changes) so you'll struggle updating all the components manually. You'd rather read'n'write localStorage only in Consultation or use a reactive state storage like Redux and MobX (in more complex applications). Local storage may be used at the same time: read on an application start and write when something changes.

Need to render a list of items/tasks. How can I do it so old items don't get re-rendered?

I am currently pulling from a custom API every second and rendering the items I receieve onto the screen.
Some code of what that looks like:
async componentDidMount() {
await this.grab_slack_user_data()
await this.grab_items()
setInterval(() => {
this.grab_items()
}, this.state.settings.seconds_per_slack_messages_pull * 1000 )
}
grab_items() {
this.setState( { isLoading: true } )
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise( resolve => {
axios.get( url, { headers } )
.then( res => {
resolve()
} )
})
}
And in my render function, I have this:
this.state.items.map(( t, i ) => {
return <Item task={ t } key={ i } user={ this.state.slack_users[ t.usr ] } settings={ this.state.settings } />
) }
I am hitting some issues with some image flickering which would be solved if I didn't re-render items that were already on the screen.
Is there a way to detect already displayed items?
You are using index as a key. Use a unique id for each item in a list to prevent re-renders
this.state.items.map((t) => (
<Item task={t} key={t.id} user={this.state.slack_users[t.usr]} settings={this.state.settings} />
))
React needs this key to perform reconciliation - determining if an element should be reused or created essentially
Take a look at react-virtualized or the more light-weight version react-window. It's a component for efficiently rendering large lists. Specifically the lazy loading packages based off them.

MapMarker does not update description onPress immediately

I am trying to learn how to use React Native maps and I am trying to add in custom Map Markers. For some reason, when I try to use the following code, the image updates properly but the description does not update properly until the second click. The first click will show "not selected" but clicking on the same marker will show the actual text I want. How can I fix this?
Since the image is updating to the newImage, I know
this.state.selectedMarkerIndex === i
but the same equality does not apply to description for some reason?
state = {busText:[]}
fetchData=(i, index)=>{
fetch('LINK TO GET BUSES'+ i.toString() + '/buses', {method:'GET'})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
selectedMarkerIndex: index,
busText: responseJson
},
()=>{
console.log("selected index: " + this.state.selectedMarkerIndex)
}
)
console.log(JSON.stringify(this.state.busText));
console.log("_______________________________________________________");
})
}
renderMarkers = ()=>{
return busStops.stops.map((stop, i) => {
return <Marker marker
key={ `${i}` }
coordinate={{latitude: stop.location.latitude, longitude: stop.location.longitude}}
title = {stop.stopName}
image = {this.state.selectedMarkerIndex === i ? newImage : busStopImage}
description={this.state.selectedMarkerIndex === i ? JSON.stringify(this.state.busText) : "not selected"}
onPress={() => {
this.fetchData(stop.stopID, i)
console.log(this.state.selectedMarkerIndex + "i :" + i)
}
}
/>
})
}
I expect the description of the MapMarker to be updated when I click on it with what is fetched but that is not occurring.
A couple of things: To guarantee execution after setState you'll need to put your
this.setState({busText: responseJson})
in the fetchData() callback. Even better, set busText state earlier, where you're setting selectedMarkerIndex state.
Also for what it's worth, if you're having response time issues, try dropping some of your console.log() calls. Native (especially iOS) gets bogged down by them.

Resources