Skills list is not updated when new skill is added - javascript

There is an accordion of Skills and Experiences in background component. When the title on the skills accordion is clicked then the modal will pop up with skill form which when filled and submitted then the modal closes and the list of skill should get update but it is not updating. I have to refresh to see the changes in the skill list. Here is how i have done
class Background extends React.PureComponent {
state = {
show: false,
componentName: null,
activeIndex: 0
};
handleModal = (action, componentName) =>
this.setState(state => ({
show: action,
componentName
}));
render() {
const { show, activeIndex, componentName } = this.state;
return (
<React.Fragment>
<ProfileWrapper>
<Query query={BACKGROUND_QUERY}>
{({
data: { experiences, skills, languages, educations } = {},
loading
}) => {
if (loading) {
return <h1>Loading...</h1>;
} else {
return (
<Grid columns={2}>
<Accordion
index={1}
onToggle={this.handleToggle}
css="max-width: 100%; min-width: 200px;"
>
<Accordion.Title>
<Title>
Skills (
{`${skills !== undefined && skills.edges.length}`})
</Title>
</Accordion.Title>
<Accordion.Content>
<Content>
{skills !== undefined && skills.edges.length > 0 && (
<span>
{skills.edges.map(skill => {
return (
<React.Fragment key={skill["node"].id}>
<span key={skill["node"].id}>
<Chip>{skill["node"].title}</Chip>
</span>
</React.Fragment>
);
})}
</span>
)}
</Content>
</Accordion.Content>
</Accordion>
<Modal
position="centerCenter"
open={show}
onClose={() => this.handleModal(false, null)}
>
<React.Fragment>
{componentName !== null &&
componentName === "experiences" && (
<Experiences experiences={experiences} />
)}
{componentName !== null &&
componentName === "skills" && (
<Skills skills={skills} />
)}
</React.Fragment>
</Modal>
</Grid>
);
}
}}
</Query>
</ProfileWrapper>
</React.Fragment>
);
}
}
export default Background;
const Skills = ({ handleSubmit, ...props }) => {
const formSubmit = async (val, mutation) => {
const {
data: { skill: response }
} = await mutation({
variables: val
});
console.log('response', response);
if (response.success) {
props.closeModal();
toast.success("New Skill Added!");
}
};
return (
<React.Fragment>
<Mutation mutation={CREATE_SKILL}>
{mutation => {
return (
<form onSubmit={handleSubmit(val => formSubmit(val, mutation))}>
<Field name="title" label="Title" component={TextField} />
<Button>
<Button.Primary>Add Skill</Button.Primary>
<Button.Secondary>Cancel</Button.Secondary>
</Button>
</form>
);
}}
</Mutation>
</React.Fragment>
);
};
export default compose(
reduxForm({
form: "skillsProfile",
enableReinitialize: true,
destroyOnUnmount: false
})
)(Skills);
why optimistic ui update is not working here when Query is done in background component?

From the docs:
Sometimes when you perform a mutation, your GraphQL server and your Apollo cache become out of sync. This happens when the update you’re performing depends on data that is already in the cache; for example, deleting and adding items to a list. We need a way to tell Apollo Client to update the query for the list of items. This is where the update function comes in!
There's no way for Apollo to know what sort of operations your server is doing when it executes a mutation (adding one or more rows, deleting a row, etc.) -- all it has to go off is the data that's returned by the mutation. It can match this data against objects that are already in the cache and update them accordingly, but that's it. If there are any fields that were cached and need to be updated as a result of your mutation, you need to explicitly tell Apollo how to do this (side note, these could be fields that return a List of Skills, but they could be literally any other fields that were impacted by the mutation).
Your update function for adding a skill, for example, would look something like this:
update={(cache, { data: { addSkill } }) => {
// assumes addSkill resolves to a single Skill
const { skills, ...rest } = cache.readQuery({ query: BACKGROUND_QUERY });
cache.writeQuery({
query: BACKGROUND_QUERY,
data: { skills: skills.concat([addSkill]), ...rest },
});
}}
See the docs for additional examples. It's also worthwhile noting that when using readQuery or writeQuery, you will need to pass in the appropriate variables if your query takes any.
While you can refetch your queries (for example, by specifying them as part of refetchQueries), it's largely unnecessary for simple updates to the cache and obviously slower than just using update since it requires another round-trip to your server. Additionally, update even works with optimistic updates to your UI.

Related

ReactJS what is the proper way to wait for prop to load to avoid crashing my page?

all,
I am building a local website for myself for stocks. Currently I have a store that communicates with my tomcat instance to get stock market data, this works flawlessly.
on my frontend I am attempting to display my data but sometimes it works, sometimes it does not work and I get an "this child prop does not exist" so this is what I implemented:
try{
cellRend = this.cellRenderer;
columnLen = this.props.selectedStock.Revenue.length;
this.state.isLoading = false
}catch(error){
cellRend = this.cellRendererEmpty;
columnLen = 10;
}
if (this.state.isLoading === true){
return <div>Loading!</div>
}
where cellRenderer is my table, cellRendererEmpty is an empty table.
this kind of works and some times it will just display Loading! forever. so my question is what is the correct way to wait for a prop?
here is my full code:
const dispatchToProps = dispatch => {
return{
getSelStock: (stockId) => dispatch(stockActions.getSelStock(stockId))
};
}
class stockPage extends React.Component{
constructor(props) {
super(props);
this.state = {
columnLen:10,
data:null,
isLoading:true
}
console.log(this.props.isLoading)
this.cellRenderer = this.cellRenderer.bind(this);
this.render = this.render.bind(this);
}
cellRenderer({ columnIndex, key, rowIndex, style }) {
return (
<div className={"app"} key={key} style={style}>
<span></span>
{rowIndex === 0 ? (`${this.props.selectedStock.Revenue[columnIndex].date}`) : (
<span>{`${this.props.selectedStock.Revenue[columnIndex].value}`}</span>
)}
</div>
);
}
cellRendererEmpty({ columnIndex, key, rowIndex, style }) {
return (
<div className={"app"} key={key} style={style}>
{rowIndex === 0 ? (`${columnIndex}`) : (
<span>{`${columnIndex}`}</span>
)}
</div>
);
}
render() {
var cellRend, columnLen
console.log("Hey")
this.props.getSelStock(this.props.match.params.stockId);
try{
cellRend = this.cellRenderer;
columnLen = this.props.selectedStock.Revenue.length;
this.state.isLoading = false
}catch(error){
cellRend = this.cellRendererEmpty;
columnLen = 10;
}
if (this.state.isLoading === true){
return <div>Loading!</div>
}
return(
<div>
<h1>{this.props.match.params.stockId}</h1>
<AutoSizer disableHeight>
{({ width }) => (
<MultiGrid
cellRenderer={cellRend}
columnWidth={125}
columnCount={this.state.columnLen}
enableFixedColumnScroll ={1}
enableFixedRowScroll ={1}
fixedColumnCount
fixedRowCount
height={300}
rowHeight={70}
rowCount={2}
style={STYLE}
styleBottomLeftGrid={STYLE_BOTTOM_LEFT_GRID}
styleTopLeftGrid={STYLE_TOP_LEFT_GRID}
styleTopRightGrid={STYLE_TOP_RIGHT_GRID}
width={width}
hideTopRightGridScrollbar
hideBottomLeftGridScrollbar
hideBottomRightGridScrollbar
/>
)}
</AutoSizer>
</div>
)
}
}
export default connect(mapStateToProps, dispatchToProps)(stockPage);
From your title, I assume that when your page loads, you are fetching data then you use that data in the page. However, during initial load and when your fetching is still in process, your data is still null and your app will crash because the code is expecting data to have a value which it needs to use...
What you can do is while the data is fetching, then do not display the rest of the page yet (ie. you can just display a giant spinner gif), then once the fetching is complete then update isLoading state... Or you can set an initial value for the data so the page won't crash on load...
EDIT:
so using react lifecycle fixed your problem as per your comment... anyways just wanna add that you may want to use async/await instead of setTimeout like you did in your comment.. This is what the code might look like for async/await in lifecycles...
componentDidMount() {
const fetchData = async () {
await this.props.getSelStock() // your dispatch
// the state you want to update once the dispatch is done
this.setState({isLoading: false})
}
fetchData();
}

React functional component not updating on setState from parent component

My React component uses apollo to fetch data via graphql
class PopUpForm extends React.Component {
constructor () {
super()
this.state = {
shoptitle: "UpdateMe",
popupbodyDesc: "UpdateMe"
}
}
render()
{
return (
<>
<Query query={STORE_META}>
{({ data, loading, error, refetch }) => {
if (loading) return <div>Loading…</div>;
if (error) return <div>{error.message}</div>;
if (!data) return (
<p>Could not find metafields :(</p>
);
console.log(data);
//loop over data
var loopedmetafields = data.shop.metafields.edges
console.log(loopedmetafields)
loopedmetafields.forEach(element => {
console.log(element.node.value)
if (element.node.value === "ExtraShopDescription"){
this.setState({
shoptitle: element.node.value
});
console.log(this.state.shoptitle)
}
if (element.node.value === "bodyDesc"){
this.setState({
popupbodyDesc: element.node.value
});
console.log(this.state.popupbodyDesc)
}
});
return (
<>
<AddTodo mkey="ExtraShopDesc" namespace="ExtraShopDescription" desc={this.state.shoptitle} onUpdate={refetch} />
<AddTodo mkey="body" namespace="bodyDesc" desc={this.state.popupbodyDesc} onUpdate={refetch} />
</>
);
}}
</Query>
</>
)
}
}
export default PopUpForm
Frustratingly the functional component renders before the state is set from the query. Ideally the functional component would only render after this as I thought was baked into the apollo library but seems I was mistaken and it seems to execute synchronous rather than asynchronous
As you can see I pass the props to the child component, in the child component I use these to show the current value that someone might amend
The functional component is here
function AddTodo(props) {
let input;
const [desc, setDesc] = useState(props.desc);
//console.log(desc)
useEffect( () => {
console.log('props updated');
console.log(props)
}, [props.desc])
const [addTodo, { data, loading, error }] = useMutation(UPDATE_TEXT, {
refetchQueries: [
'STORE_META' // Query name
],
});
//console.log(data)
if (loading) return 'Submitting...';
if (error) return `Submission error! ${error.message}`;
return (
<div>
<form
onSubmit={e => {
console.log(input.value)
setDesc(input.value)
e.preventDefault();
const newmetafields = {
key: props.mkey,
namespace: props.namespace,
ownerId: "gid://shopify/Shop/55595073672",
type: "single_line_text_field",
value: input.value
}
addTodo({ variables: { metafields: newmetafields } });
input.value = input.value
}}
>
<p>This field denotes the title of your pop-up</p>
<input className="titleInput" defaultValue={desc}
ref={node => {
input = node;
}}
/>
<button className="buttonClick" type="submit">Update</button>
</form>
</div>
);
}
Now I need this component to update when the setState is called on PopUpForm
Another stack overflow answer here gives me some clues
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is
only called the first time the component renders. Never more. Meaning
that, if you re-render that component passing a different value as a
prop, the component will not react accordingly, because the component
will keep the state from the first time it was rendered. It's very
error prone.
Hence why I then implemented useEffect however the console.log in useEffect is still "updateMe" and not the value as returned from the graphql call.
So where I'm at
I need the render the functional component after the the grapql call
and I've manipulated the data, this seems to be the best approach in terms of design patterns also
or
I need setState to pass/render the functional component with the new value
As an aside if I do this
<AddTodo mkey="ExtraShopDesc" namespace="ExtraShopDescription" desc={data.shop.metafields.edges[0].node.value} onUpdate={refetch} />
It will work but I can't always expect the value to be 0 or 1 as metafields might have already defined
I think there is a simpler way than using setState to solve this. You can for example use find like this:
const shopTitleElement = loopedmetafields.find(element => {
return element.node.value === "ExtraShopDescription"
})
const shopBodyElement = loopedmetafields.find(element => {
return element.node.value === "bodyDesc"
});
return (
<>
<AddTodo mkey="ExtraShopDesc" namespace="ExtraShopDescription" desc={shopTitleElement.node.value} onUpdate={refetch} />
<AddTodo mkey="body" namespace="bodyDesc" desc={shopBodyElement.node.value} onUpdate={refetch} />
</>
);

Filtering Nested Arrays in React JS

I am attempting to filter a list of conversations by participant names. The participant names are properties inside of a participant list and the participant list is contained within a list of conversations.
So far, I have approached the problem by attempting to nest filters:
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.filter(
(participant) => {
return participant.fallback_name.toLowerCase().indexOf(
this.state.searchTerm.toLowerCase()) !== -1;
})
})
This appears to work, insofar as I can confirm (i.e. I put a whole bunch of console.logs throughout an expanded version of the above) that as the searchTerm state is updated, it returns matching the participant and the matching convo. However, filteredConvos is not correctly rendered to reflect the newly filtered array.
I am new to Javascript, React, and Stack Overflow. My best assessment is that I am incorrectly passing my filtered array items back to filteredConvos, but I honestly don't have a enough experience to know.
Any assistance is deeply appreciated.
Further context:
The data source I'm working with is a JSON file provided by
google of an account's Hangouts chat.
HangoutSearch.js:
class HangoutSearch extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
searchTerm: e.target.value
});
}
render() {
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.filter(
(participant) => {
return participant.fallback_name.toLowerCase().indexOf(
this.state.searchTerm.toLowerCase()) !== -1;
})
})
return(
<div>
<Form>
<Form.Control
placeholder='Enter the name of the chat participant'
value={this.state.searchTerm}
onChange={this.handleChange} />
</Form>
<HangoutList filteredConvos={filteredConvos}/>
</div>
);
}
}
export default HangoutSearch;
HangoutList.js
class HangoutList extends Component {
render() {
return(
<ListGroup>
{this.props.filteredConvos.map((convo) => {
return (
<ListGroup.Item key={convo.conversation.conversation.id.id}>
{convo.conversation.conversation.participant_data.map(
(participant) => {
return (
<span key={participant.id.gaia_id}>
{participant.fallback_name}
</span>
)
}
)}
</ListGroup.Item>
)
})}
</ListGroup>
);
}
}
export default HangoutList;
The inner .filter always returns an array, which are truthy in Javascript. You could use .some instead:
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.some((participant) => {
return participant.fallback_name.toLowerCase().indexOf( this.state.searchTerm.toLowerCase()) !== -1;
})
})

How can I return a new array based on a search criteria?

I am trying to figure out how can I develop a search box.
I have this array passengersData which is coming from a redux store.
All of the examples about search functionality on React I've seen are based on local state with the search input in the same component where the rendered data array lives. But I need to pass the value of the search box to the child component, and then, what can I do to perform the search?
This is the child component:
// imports
const PassengerCardBasedOnRoute = ({
passengersData,
searchParam,
}) => {
return (
<View>
{passengersData &&
passengersData.map(info => (
<PassengersInfo
key={info.id}
searchParam={searchParam}
name={info.name}
callModal={popupsModalsActionHandler}
/>
))}
</View>
);
};
// Props validatiom
export default compose(
connect(
store => ({
passengersData: store.homeScreen.passengersData,
}),
dispatch => ({
popupsModalsActionHandler: data => {
dispatch(popupsModalsAction(data));
},
}),
),
)(PassengerCardBasedOnRoute);
And this is the component that will hold the search box:
// imports
// CHILD COMPONENT
import PassengerCardBasedOnRoute from '../PassengerInfo/PassengerCardBasedOnRoute';
class AllPassengersList extends Component {
state = { searchParam: '' };
render() {
const { searchParam } = this.state;
return (
<View>
{isVisible && (
<View>
<TextInput
onChangeText={text => this.setState({ searchParam: text })}
value={searchParam}
/>
</View>
)}
<PassengerCardBasedOnRoute searchParam={searchParam} />
</View>
);
}
}
// Props validation
export default compose(
connect(store => ({
navigationStore: store.homeScreen.navigation,
passengersData: store.homeScreen.passengersData,
})),
)(AllPassengersList);
So, based on my code, how can I grab the value and what should I do to perform the search across passengersData array?
Also, as you may see, passengersData is coming from a redux store.
There are a couple of options you can do to achieve what you want. The first is to simply augment your map to also include a filter. For example:
{passengersData && passengersData
.filter(info => {
// return true here if info matches the searchParam
})
.map(info => ( ...
The other is to move your search params from local state to redux, and perform the filter inside an action that responds to the search param being changed. For simplicity I would go with the first option for now, but if it turns out you need to access the search param in other parts of your app, or do something more complex (like optimise rendering or something), I would move the search stuff into redux.

Ensure parent only displays when all childs are loaded

I am very new to react and I'm struggling with the following. I created a form in react and that form contains a dropdown. I need to reuse that dropdown in multiple pages so I thought to make it a component that is fully responsible to get all data.
In the form component I get all data and one of those fields is the selectedServerDataId. The selectId field contains the ID of the value that needs to be selected in the dropdown.
<snip includes/>
class Arrival extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
formData: {}
};
}
async componentDidMount() {
await fetch(urls.addEditUrl)
.then(response => response.json())
.then(data => {
this.setState({ formData: data });
});
}
<snip some setstate helpers/>
render() {
const { formData } = this.state;
return (
<Form >
<Selector label="select"
onChange={this.UpdateFormSelectData.bind(this, 'selectedServerDataId')}
value={formData.selectedServerDataId}/>
<DateItem label="date"
allowClear={true}
value={formData.date}
onChange={this.updateFormDateData.bind(this, 'date')}/>
<snip more fields.../>
</Form>);
}
}
export default Arrival;
The fetch in the parent component retrieves the data for the edit form including the selectedServerDataId. My child component looks like below:
<snip usings />
class ServerDataSelector extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [],
};
}
async componentDidMount() {
await fetch(URLS.GetServerData)
.then(response => response.json())
.then(data => {
this.setState({ options: data });
});
}
render() {
const { options } = this.state;
return (
<FormItem {...formItemLayout} label={t(this.props.label)} >
<Select setValue={this.props.onChange} getValue={() => this.props.value} selectedOption={this.props.value} name={this.props.label}>
{options.map(d => <Option key={d.id} value={d.id}>{d.value}</Option>)}
</Select>
//TODO add model window to manipulate list.
</FormItem>);
}
}
export default ServerDataSelector
ServerDataSelector { Selector }
Currently when the page renders I first see the ID of the selected value in the dropdown and a split second later I see the actual selected value label.
Is there a way to ensure that the parent component only renders when the childs are completely done loading?
The main issue you have here is that you have two components that have a strict rendering hierarchical relationship (parent -> child) but equal data fetching responsibility.
This leads to the situation where you have to react to data retrieval between multiple components in a specific order and is a non-trivial concurrency problem. One solution would be to use a shared state container (using Redux or the Context API) and have both components save their results into this shared state bucket and then whenever the bucket has all the data, you present it (until then you can show a spinner for example).
A simpler solution would be to just move the fetching into one of the components, eliminate this split data-fetching responsibility altogether, and allow the component fetching the data to also control the rendering.
One way is to get all the data in the parent and have the child just be a pure component that is rendered when the data is received:
class Arrival extends React.Component {
// ...
async componentDidMount() {
const [formData, options] = await Promise.all([
fetch(urls.addEditUrl).then(response => response.json()),
fetch(URLS.GetServerData).then(response => response.json())
]);
this.setState({ formData, options });
}
render() {
const { formData, options } = this.state;
return (
<Form>
{/* ... */}
{formData &&
options && (
<Selector
label="select"
options={this.state.options} {/* pass the options here */}
onChange={this.UpdateFormSelectData.bind(
this,
"selectedServerDataId"
)}
value={formData.selectedServerDataId}
/>
)}
</Form>
);
}
}
Selector is now only a presentational component, no need for state:
const ServerDataSelector = props => (
<FormItem {...formItemLayout} label={t(this.props.label)}>
<Select
setValue={this.props.onChange}
getValue={() => this.props.value}
selectedOption={this.props.value}
name={this.props.label}
>
{this.props.options.map(d => ( {/* use props.options */}
<Option key={d.id} value={d.id}>
{d.value}
</Option>
))}
</Select>
</FormItem>
);
You could optionally show a spinner or a loading indicator:
...
{
formData && options ? (
<Selector
label="select"
onChange={this.UpdateFormSelectData.bind(this, "selectedServerDataId")}
options={this.state.options}
value={formData.selectedServerDataId}
/>
) : (
<Spinner />
);
}
Alternatively, you can have the child fetch all the data and the parent would just render the child.
class ServerDataSelector extends React.Component {
// ...
async componentDidMount() {
const [formData, options] = await Promise.all([
fetch(urls.addEditUrl).then(response => response.json()),
fetch(URLS.GetServerData).then(response => response.json())
]);
this.setState({
value: formData.selectedServerDataId,
options
});
}
render() {
return (
<FormItem {...formItemLayout} label={t(this.props.label)}>
<Select
setValue={this.props.onChange}
getValue={() => this.state.value} {/* use the value from the state */}
selectedOption={this.state.value} {/* use the value from the state */}
name={this.props.label}
>
{this.state.options.map(d => (
<Option key={d.id} value={d.id}>
{d.value}
</Option>
))}
</Select>
</FormItem>
);
}
}
You could also add a spinner within the child to prevent page jank and allow for a nicer UX:
render() {
if (!this.state.value || !this.state.options) {
return <Spinner />;
}
return (
<FormItem {...formItemLayout} label={t(this.props.label)}>
<Select
setValue={this.props.onChange}
getValue={() => this.state.value}
selectedOption={this.state.value}
name={this.props.label}
>
{this.state.options.map(d => (
<Option key={d.id} value={d.id}>
{d.value}
</Option>
))}
</Select>
</FormItem>
);
}
After many tries and errors I found a way to fix this.
In my parent state I added the following object:
this.state = {
formData: null,
loadedComponents: {
parentComponentIsLoaded: false,
childComponent1IsLoaded: false,
childComponent2IsLoaded: false,
}
};
Then I added the following 2 methods in my parent
componentLoaded = (field, data) => {
const { loadedComponents } = this.state;
var fieldName = field + "IsLoaded";
this.setState({ loadedComponents: { ...loadedComponents, [fieldName]: true } });
}
allComponentsLoaded() {
const { loadedComponents } = this.state;
console.log(loadedComponents);
for (var o in loadedComponents) {
if (!loadedComponents[o]) return false;
}
return true;
}
Changed the render method to this:
return (
formData ?
<Form className={this.allComponentsLoaded() ? '' : 'hidden'} >
<ChildComponet1 {someprops} isLoaded={this.componentLoaded}/>
<ChildComponent2 {someprops} isLoaded={this.componentLoaded}/>
<snip />
</Form> : <div />);
And added the following line in the childs componentDidMount method after the fetching of the data.
this.props.isLoaded('childComponet1', true)
Maybe this is very bad react design and should you actually use MEM035's answer, but it does work

Categories

Resources